<?php

namespace Largezhou\Admin\Form;

use Closure;
use DB;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\Relation;
use Illuminate\Support\Collection;
use Largezhou\Admin\Components\Component;
use Largezhou\Admin\Components\Tools\ToolManager;
use Largezhou\Admin\Traits\HeaderTools;
use Symfony\Component\HttpFoundation\Response;
use Request;

/**
 * @method Fields\Input input(string $fieldName, string $label)
 * @method Fields\Select select(string $fieldName, string $label)
 * @method Fields\SwitchField switch (string $fieldName, string $label)
 * @method Fields\TreeSelect treeSelect(string $fieldName, string $label)
 * @method Fields\Upload upload(string $fieldName, string $label)
 * @method Fields\MultipleSelect multipleSelect(string $fieldName, string $label)
 */
class Form extends Component
{
    use HeaderTools;

    const MODE_VIEW = 'view';
    const MODE_EDIT = 'edit';
    const MODE_CREATE = 'create';

    const UPLOAD_FLAG = '_upload_field';

    protected static $availableFields = [];
    protected $fields;
    protected $mode = 'create';
    protected $labelWidth = '120px';
    protected $props = [
        'labelWidth' => '120px',
    ];

    /**
     * @var null|Model
     */
    protected $model = null;

    /**
     * 模型id
     *
     * @var
     */
    protected $id;

    /**
     * 表单提交的url.
     *
     * @var string
     */
    protected $action = '';

    /**
     * 表单保存后的回调
     *
     * @var null|Closure
     */
    protected $saved = null;

    /**
     * 编辑或查看时,模型的数据
     *
     * @var array
     */
    protected $data = [];

    /**
     * 存储数据前的回调
     *
     * @var null|Closure
     */
    protected $saving = null;

    /**
     * 保存,更新,删除后的回调
     *
     * @var null|Closure
     */
    protected $completed = null;

    /**
     * 添加或更新时,请求中传来的数据
     *
     * @var array
     */
    protected $inputs = [];

    /**
     * 请求中的数据中,关联关系的值
     *
     * @var array
     */
    protected $relations = [];

    public function __construct(Model $model = null)
    {
        $this->model = $model;

        $this->fields = new Collection();

        $this->setupDefaultTools();
    }

    /**
     * 获取模型转成数组后的所有数据.
     *
     * @return array
     */
    public function getData()
    {
        return $this->data;
    }

    protected function setupDefaultTools()
    {
        $this->headerTool($this->toolManager()->back());
        // 由于初始化时，没有表单的模式始终为create，
        // 所以这里采用回调的方式，让工具的构建，延迟到返回响应前。
        $this->headerTool(function (ToolManager $toolManager) {
            return $toolManager->link(
                '列表',
                $this->isMode(static::MODE_EDIT) ? route_path(-2) : route_path(-1)
            );
        });
    }

    public static function registerBuiltinFields()
    {
        $fieldsMap = require __DIR__.'/../data/builtin-fields.php';

        foreach ($fieldsMap as $field => $class) {
            static::extendField($field, $class);
        }
    }

    public static function extendField(string $field, string $class)
    {
        static::$availableFields[$field] = $class;
    }

    public function __call($name, $arguments)
    {
        if ($className = static::getFieldClassName($name)) {
            $column = array_get($arguments, 0, '');
            $field = new $className($column, ...array_slice($arguments, 1));
            $this->pushField($field);

            return $field;
        }

        return parent::__call($name, $arguments);
    }

    /**
     * @param string $field
     *
     * @return bool|string
     */
    protected static function getFieldClassName(string $field)
    {
        $class = array_get(static::$availableFields, $field);

        return class_exists($class) ? $class : false;
    }

    /**
     * @param Field $field
     *
     * @return $this
     */
    protected function pushField(Field $field)
    {
        $field->setForm($this);
        $this->fields()->push($field);

        return $this;
    }

    public function fields(): Collection
    {
        return $this->fields;
    }

    public function variables()
    {
        return [
            'props'     => array_merge($this->props(), [
                'action'      => $this->action(),
                'method'      => $this->method(),
                'fields'      => $this->fields()->toArray(),
                'headerTools' => $this->headerTools(),
            ]),
            'component' => $this->component(),
        ];
    }

    /**
     * 设置或者获取表单的action属性值
     *
     * @param string $action
     *
     * @return $this|string
     */
    public function action(string $action = null)
    {
        if (is_null($action)) {
            if ($this->action) {
                return $this->action;
            }
            if ($this->isMode(static::MODE_EDIT)) {
                return route_path(-2).'/'.$this->id;
            }
            if ($this->isMode(static::MODE_CREATE)) {
                return route_path(-1);
            }

            return '';
        }

        $this->action = $action;

        return $this;
    }

    public function isMode(string $mode): bool
    {
        return $this->mode === $mode;
    }

    public function setMode(string $mode)
    {
        $this->mode = $mode;

        return $this;
    }

    public function method(): string
    {
        if ($this->isMode(static::MODE_EDIT)) {
            return 'put';
        }
        if ($this->isMode(static::MODE_CREATE)) {
            return 'post';
        }

        return '';
    }

    public function store()
    {
        if (($response = $this->handleUpload()) instanceof Response) {
            return $response;
        }

        $this->inputs = Request::all();

        $this->removeFieldsNotInForm();
        $this->prepareFieldValue();
        $this->removeNullValues();

        $this->separateRelationInputs();

        if (($response = $this->call()) instanceof Response) {
            return $response;
        }

        DB::transaction(function () {
            $this->model->fill($this->inputs);
            $this->model->save();

            $this->updateRelation();
        });


        if (($response = $this->call($this->saved)) instanceof Response) {
            return $response;
        }

        return $this->response([
            'status' => true,
            'msg'    => '添加成功',
            'to'     => route_path(),
        ]);
    }

    protected function response($data)
    {
        if (($response = $this->call($this->completed, $data)) instanceof Response) {
            return $response;
        }

        return response()->json($data);
    }

    /**
     * 处理文件上传
     *
     * @return \Illuminate\Http\JsonResponse
     */
    protected function handleUpload()
    {
        if (empty($files = Request::allFiles())) {
            return;
        }

        $fieldNames = array_keys($files);
        $fieldName = array_shift($fieldNames);

        $field = $this->getFieldByName($fieldName);

        if (!$field) {
            abort(404);
        }

        return response()->json($field->storeFile());
    }

    protected function getFieldByName($fieldName)
    {
        return $this->fields()->first(
            function (Field $field) use ($fieldName) {
                if (is_array($field->name())) {
                    return in_array($fieldName, $field->name());
                }

                return $field->name() === $fieldName;
            }
        );
    }

    /**
     * 从输入数据中，移除不在表单中的字段.
     */
    protected function removeFieldsNotInForm()
    {
        $fieldsInForm = $this->fields()
                             ->map(function (Field $field) {
                                 return $field->name();
                             })
                             ->toArray();

        $this->inputs = array_only($this->inputs, $fieldsInForm);
    }

    /**
     * 调用各组件的prepare方法,处理数据
     */
    protected function prepareFieldValue()
    {
        $this->fields()
             ->each(function (Field $field) {
                 $this->inputs[$field->name()] = $field->prepare($this->inputs[$field->name()] ?? $field->value());
             });
    }

    /**
     * 移除所有null值
     */
    protected function removeNullValues()
    {
        $this->inputs = array_filter($this->inputs, function ($v) {
            return $v !== null;
        });
    }

    /**
     * 把请求中的数据中的关联关系的值,分离出来
     */
    protected function separateRelationInputs()
    {
        foreach ($this->fields() as $field) {
            /** @var Field $field */
            $fieldName = $field->name();
            if (method_exists($this->model, $fieldName)) {
                $relation = call_user_func([$this->model, $fieldName]);

                if ($relation instanceof Relation) {
                    $this->relations[$fieldName] = $this->inputs[$fieldName];
                    unset($this->inputs[$fieldName]);
                }
            }
        }
    }

    /**
     * 更新关联关系的值
     */
    protected function updateRelation()
    {
        foreach ($this->relations as $name => $value) {
            $relation = call_user_func([$this->model, $name]);

            switch (get_class($relation)) {
                case \Illuminate\Database\Eloquent\Relations\BelongsToMany::class:
                case \Illuminate\Database\Eloquent\Relations\MorphToMany::class:
                    /**
                     * @var \Illuminate\Database\Eloquent\Relations\BelongsToMany|\Illuminate\Database\Eloquent\Relations\MorphToMany $relation
                     */
                    $relation->sync($value);
                    break;
            }
        }
    }

    public function labelWidth($width = null)
    {
        if (is_null($width)) {
            return $this->labelWidth;
        }

        $this->props('labelWidth', $width);

        return $this;
    }

    public function edit()
    {
        $this->setMode(static::MODE_EDIT);
        $this->id($this->model->getKey());

        $this->setFieldValue();

        return $this;
    }

    public function id($id)
    {
        $this->id = $id;
    }

    protected function setFieldValue()
    {
        $relationNames = $this->getRelations();

        $this->model->load($relationNames);
        $this->data = $this->model->makeVisible($this->model->getHidden())->toArray();

        $this->fields()->each(function (Field $field) {
            $field->value(array_get($this->data, $field->name()));
        });
    }

    /**
     * 获取字段中所有的关联字段的字段名
     *
     * @return array
     */
    protected function getRelations()
    {
        $names = [];

        foreach ($this->fields() as $field) {
            /** @var Field $field */
            $name = $field->name();
            if (method_exists($this->model, $name)) {
                $relation = call_user_func([$this->model, $name]);

                if ($relation instanceof Relation) {
                    $names[] = $name;
                }
            }
        }

        return $names;
    }

    /**
     * 设置表单保存后的回调
     *
     * @param Closure $callback
     *
     * @return $this
     */
    public function saved(Closure $callback)
    {
        $this->saved = $callback;

        return $this;
    }

    public function destroy()
    {
        $res = $this->model->delete();

        return $this->response([
            'status' => $res,
            'msg'    => $res ? '删除成功' : '删除失败',
        ]);
    }

    public function update()
    {
        $this->setMode(static::MODE_EDIT);

        $this->inputs = Request::all();

        $this->removeFieldsNotInForm();
        $this->prepareFieldValue();

        $this->separateRelationInputs();
        $this->updateRelation();

        if (($response = $this->call()) instanceof Response) {
            return $response;
        }

        $this->model->fill($this->inputs);
        $this->model->save();

        if (($response = $this->call($this->saved)) instanceof Response) {
            return $response;
        }

        return $this->response([
            'status' => true,
            'msg'    => '更新成功',
            'to'     => route_path(-1),
        ]);
    }

    /**
     * 获取或者设置inputs中的数据.
     *
     * @param string $key
     * @param null   $value
     *
     * @return array|mixed
     */
    public function inputs($key, $value = null)
    {
        if (is_null($value)) {
            return array_get($this->inputs, $key);
        }

        return array_set($this->inputs, $key, $value);
    }

    public function model()
    {
        return $this->model;
    }

    /**
     * 设置保存数据前的回调
     *
     * @param Closure $callback
     *
     * @return $this
     */
    public function saving(Closure $callback)
    {
        $this->saving = $callback;

        return $this;
    }

    /**
     * 调用回调函数,并传入Form实例
     *
     * @param Closure|null $callback
     * @param array        $data Form中自带的数据
     *
     * @return mixed
     */
    protected function call(Closure $callback = null, $data = [])
    {
        if ($callback instanceof Closure) {
            return call_user_func($callback, $this, $data);
        }
    }

    /**
     * 设置更新,添加和删除后的回调
     *
     * @param Closure $callback
     *
     * @return $this
     */
    public function complete(Closure $callback)
    {
        $this->completed = $callback;

        return $this;
    }
}
